package terrain;


import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;

import javax.swing.JOptionPane;

import deroulementjeu.TypeEquipe;

import unite.Pokemon;
import unite.TypeUnite;
import unite.Unite;


/**
 * Représente un itinéraire (succession de CaseTerrain) pour le déplacement
 * d'une unité
 * @author Alexis BRASY, Geoffrey LEQUESNE, Sandrine PIRIOU, Michel POPOV
 *
 */
public class Itineraire implements Serializable {
    private static final long serialVersionUID = 1L;
	
    /** Pokémon pour lequel on crée l'itinéraire */
    private Pokemon unite;

    /** Liste des cases composant l'itinéraire */
    private ArrayList<CaseTerrain> liste;

    /** Terrain auquel appartiennent les cases */
    private Terrain terrain;

    /** Nombre de points de déplacement disponibles */
    private int deplacement;

    /** Nombre de points de déplacement nécessaires pour l'itinéraire */
    private int coutDeplacement;

    /**
     * Constructeur d'un itinéraire avec un paramètre (l'unité pour laquelle
     * on construit l'itinéraire)
     * /!\ Il faudra vérifier que le pokémon a bien des points de déplacement avant de 
     * pouvoir commencer à créer un itinéraire
     * @param unite l'unité dont on construit l'itinéraire
     */
    public Itineraire(Pokemon unite) {
        this.unite = unite;
        this.terrain = unite.getCaseTerrain().getTerrain();
        this.deplacement = unite.getDeplacement();
        liste = new ArrayList<CaseTerrain>();
        liste.add(liste.size(), unite.getCaseTerrain());
    }

    /**
     * Ajoute une case à l'itinéraire en vérifiant que c'est bien une case adjacente à la 
     * dernière case de l'itinéraire, à partir de coordonnées.
     * @param coord Coordonnées de la case à ajouter à l'itinéraire.
     */
    public void ajouterCase(Coordonnees coord) {
        // Déterminer la case terrain qu'on veut ajouter à partir des coordonnées
        CaseTerrain caseT = terrain.getCase(coord);
		
        // Déterminer le nombre de points de déplacement nécessaire pour accéder à cette case
        int nb = terrain.getRegles().getCoutDepl(caseT.getTypeTerrain(),
                unite.getType());
		
        // Ajout de la case à l'itinéraire si les deux cases sont bien adjacentes et
        // qu'il reste assez de points de déplacement pour se déplacer sur cette case
        
        if (liste.get(liste.size() - 1).caseAdjacente(caseT)
                && nb <= deplacement) {
            liste.ensureCapacity(liste.size() + 1);
            liste.add(liste.size(), caseT);
            deplacement -= nb;
            coutDeplacement += nb;
        }
    }
	
    /**
     * Ajoute une case à l'itinéraire sans vérification de case adjacente ou de possibilité
     * de déplacement
     * (utilisée pour la création d'un itinéraire pour l'IA)
     * @param coord coordonnées de la case à ajouter à l'itinéraire
     */
    public void ajouterCaseSansDepl(Coordonnees coord) {
        CaseTerrain caseT = terrain.getCase(coord);
		
        liste.ensureCapacity(liste.size() + 1);
        liste.add(liste.size(), caseT);
    }

    /**
     * Ajoute une case à l'itinéraire en vérifiant que c'est bien une case adjacente à la 
     * dernière case de l'itinéraire
     * @param x abscisse de la case qu'on veut ajouter
     * @param y ordonnée de la case qu'on veut ajouter
     */
    public void ajouterCase(int x, int y) {
        ajouterCase(new Coordonnees(x, y));
    }
	
    /**
     * Réinitialise l'itinéraire (vide la liste et ajoute la position initiale du pokémon)
     */
    public void reinitialiser() {
        liste.clear();
        liste = new ArrayList<CaseTerrain>();
        liste.add(unite.getCaseTerrain());
    }
	
    /**
     * Vide complètement la liste sans remettre la position initiale
     * (utile pour la fonction qui calcule l'itinéraire optimal vers une case)
     */
    public void reinitialiserCompletement() {
        liste.clear();
        liste = new ArrayList<CaseTerrain>();
    }

    /**
     * Valide l'itinéraire
     */
    public void valider() {
        int i;

        i = 1;
		
        ArrayList<CaseTerrain> casesAffichables = unite.getEquipe().getCasesAffichables();
        boolean estPresqueMort = unite.estPresqueMort();

        // Tant qu'il reste des cases à parcourir et que l'unité peut encore se déplacer (si l'unité effectue un combat, son déplacement est mis à zéro)
        while (i < liste.size() && unite.getDeplacement() > 0) {
            if (liste.get(i).caseVide()) {
                unite.getCaseTerrain().retirerUniteSansRetirerEquipe();
                unite.setCaseTerrain(liste.get(i));
                unite.getCaseTerrain().ajouterUnite(unite);
            } else {
                // si l'unité est effectivement ennemie et qu'elle se situe dans 
                // les cases visibles
                if (unite.getEquipe() != liste.get(i).getUnite().getEquipe()
                        && casesAffichables.contains(liste.get(i))) {
                    unite.combatCorpsACorps(liste.get(i).getUnite());
                } else {
                    unite.setDeplacement(0);
                    if (unite.getEquipe().getType() == TypeEquipe.HUMAIN) {
                        if (liste.get(i).getUnite().getType()
                                == TypeUnite.POTION
                                        || liste.get(i).getUnite().getType()
                                                == TypeUnite.OBSTACLE) {
                            JOptionPane.showMessageDialog(null,
                                    "Quelque chose se trouvait sur votre chemin !",
                                    "Attention !", JOptionPane.WARNING_MESSAGE);
                        } else {
                            JOptionPane.showMessageDialog(null,
                                    "Une unité ennemie se trouvait sur votre chemin !",
                                    "Attention !", JOptionPane.WARNING_MESSAGE);
                        }
                    }

                    /*
                     else {
                     if(liste.get(i).getUnite().getType()!=TypeUnite.POTION && liste.get(i).getUnite().getType()!=TypeUnite.OBSTACLE)
                     jop.showMessageDialog(null, "L'ennemi a été surpris par une de vos unités !", "Attention !", JOptionPane.WARNING_MESSAGE);
                     }
                     */
                }
            }
            i++;
        }
        // On met le déplacement à 0 pour éviter de pouvoir créer un nouvel itinéraire
        unite.setDeplacement(0);
        // }
        if (estPresqueMort) {
            unite.deplacement(coutDeplacement);
        }
    }

    public void setDeplacement(int deplacement) {
        this.deplacement = deplacement;
    }

    public int getDeplacement() {
        return deplacement;
    }

    /**
     * Renvoie la liste des cases sélectionnables dans l'immédiat
     * @return Collection contenant les cases sélectionnables.
     */
    public Collection<CaseTerrain> getListeCasesTerrainSelectionnables() {
        ArrayList<CaseTerrain> vide = new ArrayList<CaseTerrain>();
		
        CaseTerrain derniereCaseSelec = liste.get(liste.size() - 1);

        // si la dernière case sélectionnée n'est pas vide (ie contient une unité), et est dans la zone visible par le joueur, ou bien que l'on a plus de points de déplacements, alors on retourne une liste de cases sélectionnables vide.
        if ((liste.size() > 1 && !derniereCaseSelec.caseVide()
                && unite.getEquipe().getCasesAffichables().contains(
                derniereCaseSelec))
                        || unite.getDeplacement() == 0) {
            return vide;
        }
		
        ArrayList<CaseTerrain> selection = new ArrayList<CaseTerrain>(
                this.liste.get(this.liste.size() - 1).getCaseAdjacente());
		
        int i;

        i = 0;
        while (i < selection.size()) {
            if (selection.get(i).getTypeTerrain() == TypeTerrain.VIDE
                    || unite.getRegles().getCoutDepl(
                            selection.get(i).getTypeTerrain(), unite.getType())
                                    > deplacement) {
                selection.remove(i);
            } else {
                i++;
            }
        }
		
        ArrayList<CaseTerrain> allies = new ArrayList<CaseTerrain>();

        allies.ensureCapacity(unite.getEquipe().getUnites().size());
        for (Unite u : unite.getEquipe().getUnites()) {
            allies.add(u.getCaseTerrain());
        }
        selection.removeAll(allies);
		
        return selection;
    }
	
    /**
     * Inverse l'itinéraire
     * (utile pour la fonction qui calcule l'itinéraire optimal vers une case)
     */
    public void inverser() {
        int gauche = 0, droite = liste.size() - 1;

        while (gauche < droite) {
            CaseTerrain temp = liste.get(gauche);

            liste.set(gauche, liste.get(droite));
            liste.set(droite, temp);
            gauche++;
            droite--;
        }
    }
	
    /**
     * Méthode toString de la classe Itineraire
     * @return une chaîne de caractères décrivant l'ensemble des coordonnees parcourues
     */
    @Override
    public String toString() {
        String retour = new String();

        for (int i = 0; i < liste.size(); i++) {
            CaseTerrain cT = liste.get(i);

            retour += "(" + cT.getCoordonnees().getX() + ","
                    + cT.getCoordonnees().getY() + ") - ";
        }
        return	retour;
    }

    /**
     * Accesseur sur la liste des cases qui composent l'itinéraire
     * @return ArrayList<CaseTerrain>
     */
    public ArrayList<CaseTerrain> getListeCases() {
        return liste;
    }
}
